package xyz.xiezc.ioc;

import xyz.xiezc.ioc.annotation.*;
import xyz.xiezc.ioc.common.AnnotationHandler;
import xyz.xiezc.ioc.common.BeanDefine;
import xyz.xiezc.ioc.common.XIocException;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 超级简单的依赖注入小框架
 *
 * @author wb-xzc291800
 * @date 2019/03/29 14:17
 */
public abstract class XIoc {

    /**
     * 装载依赖的容器. 当依赖全部注入完成的时候,这个集合会清空
     */
    List<BeanDefine> container = new ArrayList<>();

    /**
     * 装载类上面有注解的各个类,  方便其他程序识别注解, 不用再次扫描类了.
     * 当依赖全部注入完成的时候,这个集合会清空
     */
    Map<Class, Annotation[]> annotationMap = new ConcurrentHashMap<>();

    /**
     * 装载用户的自定义注解, 并且注解还是使用了@Component 来注解这个注解
     */
    List<Class<?>> customAnno = new ArrayList<>();


    protected XIoc() {
        try {
            this.praseAnnotation();
        } catch (Exception e) {
            throw new XIocException(e);
        }
    }

    /**
     * 解析注解
     */
    private void praseAnnotation() throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException {
        //扫描当前包路径下的所有class文件
        List<String> classFiles = new ArrayList<>();
        ComponentScan componentScan = this.getClass().getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            String[] value = componentScan.value();
            for (String val : value) {
                classFiles.addAll(findAllClassName(Class.forName(val)));
            }
        }

        classFiles.addAll(findAllClassName(this.getClass()));
        //加载所有扫到的class文件, 并根据注解实例化到容器中
        for (String className : classFiles) {
            initBeanDefine(className);
        }

        BeanDefine beanDefine = new BeanDefine();
        beanDefine.settClass(this.getClass());
        String typeName = this.getClass().getSimpleName();
        typeName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
        beanDefine.setName(typeName);
        beanDefine.setBean(this);
        container.add(beanDefine);

        for (String className : classFiles) {
            Class<?> aClass = Class.forName(className);
            Annotation annotation = null;
            for (Class clazz : customAnno) {
                annotation = aClass.getAnnotation(clazz);
                if (annotation == null) {
                    continue;
                } else {
                    break;
                }
            }
            if (annotation != null) {
                addToContainer(aClass, "", annotation);
            }
        }


        //在注入依赖之前先把Configuration注解中 方法的bean放入容器
        getMethodBean();
        //注入依赖
        injectAll();
        //调用init方法
        excuteInitMethod();
        //自定义注解处理器的调用
        dealAnnotationHandler();

        container.clear();
        container = null;
        annotationMap.clear();
        annotationMap = null;
    }

    private List<String> findAllClassName(Class clazz) throws IOException {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
        String aPackage = clazz.getPackage().getName();
        String prefixPackagePath = aPackage.replaceAll("\\.", "/");
        List<String> classFiles = new ArrayList<>();
        if (path.endsWith(".jar")) {
            try (JarFile jarFile = new JarFile(path)) {
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry jarEntry = entries.nextElement();
                    String name = jarEntry.getName();
                    if (name.endsWith(".class") && name.startsWith(prefixPackagePath)) {
                        name = name.substring(0, name.length() - 6);
                        classFiles.add(name.replaceAll("/", "."));
                    }
                }
            }
        } else {
            File file = new File(path + prefixPackagePath);
            classFiles = finalClassName(file, aPackage);
        }
        return classFiles;
    }

    /**
     * 将容器中方法的Configuration 注解的类中的方法的bean放入容器中
     *
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void getMethodBean() throws IllegalAccessException, InvocationTargetException {
        List<BeanDefine> defines = new ArrayList<>();
        for (BeanDefine entry : container) {
            Annotation annotation = entry.getAnnotation();
            if (annotation == null) {
                //主类
                continue;
            }
            Configuration configuration = null;
            if (annotation instanceof Configuration) {
                configuration = (Configuration) annotation;
            }

            //不为空
            if (configuration != null) {
                Class key = entry.gettClass();
                Method[] declaredMethods = key.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    Component component = declaredMethod.getAnnotation(Component.class);
                    if (component != null) {
                        String beanName = component.value();
                        if (Objects.equals("", beanName)) {
                            beanName = declaredMethod.getName();
                        }
                        Object invoke = declaredMethod.invoke(entry.getBean());
                        addToContainer(declaredMethod.getReturnType(), beanName, component, invoke, defines);
                    }
                }
            }
        }
        container.addAll(defines);
    }

    /**
     * 逐个调用用户定义的自定义注解处理器
     */
    private void dealAnnotationHandler() {
        for (BeanDefine beanDefine : container) {
            Class<?> aClass = beanDefine.gettClass();
            boolean assignableFrom = AnnotationHandler.class.isAssignableFrom(aClass);
            if (assignableFrom) {
                //获得容器中的注解处理器
                AnnotationHandler annotationHandler = (AnnotationHandler) beanDefine.getBean();
                //获得自定义处理器处理的注解
                Class handlerAnnotation = annotationHandler.getHandlerAnnotation();
                Set<Map.Entry<Class, Annotation[]>> entries = annotationMap.entrySet();
                //遍历注解, 找到自定义处理器处理的注解
                for (Map.Entry<Class, Annotation[]> entry : entries) {
                    Annotation[] value = entry.getValue();
                    Class key = entry.getKey();
                    for (Annotation annotation1 : value) {
                        //找到了自定义处理器处理的注解了
                        if (handlerAnnotation.isAssignableFrom(annotation1.getClass())) {
                            //检查这个注解注释的类是否已经实例化到容器中
                            List<BeanDefine> collect = this.container.stream()
                                    .filter(beanDefine1 -> {
                                        Class aClass1 = beanDefine1.gettClass();
                                        boolean equals = Objects.equals(key, aClass1);
                                        if (equals) {
                                            return true;
                                        }
                                        return false;
                                    }).collect(Collectors.toList());

                            annotationHandler.process(entry.getKey(), annotation1, collect);
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 调用的 bean类的初始化方法
     *
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void excuteInitMethod() throws IllegalAccessException, InvocationTargetException {
        for (BeanDefine beanDefine : container) {
            Class<?> aClass = beanDefine.getBean().getClass();
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                InitMethod annotation = method.getAnnotation(InitMethod.class);
                if (annotation != null) {
                    method.invoke(beanDefine.getBean());
                }
            }
        }
    }

    /**
     * 注入所有的依赖
     */
    private void injectAll() throws ClassNotFoundException {
        for (BeanDefine beanDefine : container) {
            Class<?> aClass = beanDefine.getBean().getClass();
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {
                Inject annotation = field.getAnnotation(Inject.class);
                if (annotation != null) {
                    injectField(field, annotation, beanDefine);
                }
            }
        }
    }

    /**
     * 注入到某个字段
     *
     * @param field
     * @param annotation
     * @param initBeanDefine
     */
    private void injectField(Field field, Inject annotation, BeanDefine initBeanDefine) throws ClassNotFoundException {
        Class<?> type = field.getType();

        Collection<BeanDefine> canInject = new ArrayList<>();

        boolean isArray = type.isArray();
        boolean isCollection = Collection.class.isAssignableFrom(type);
        if (isArray | isCollection) {
            Class<?> objClass = annotation.type();
            if (objClass.equals(void.class)) {
                throw new RuntimeException("数组或者集合类型的字段注入必须指定数组中元素的类型");
            }
            type = objClass;
        }


        //筛选出可以注入的类型
        for (BeanDefine beanDefine : container) {
            Class<?> aClass = beanDefine.gettClass();
            //类型可以注入
            if (type.isAssignableFrom(aClass)) {
                canInject.add(beanDefine);
            }
        }

        //没有可以注入的类型
        if (canInject.isEmpty()) {
            throw new XIocException("没有找到对应的注入类, field:" + field.getName() + " ; fieldType:" + field.getType());
        }
        //数组字段的注入
        if (isArray) {
            throw new XIocException("不支持数组类型依赖注入, field:" + field.getName() + " ; fieldType:" + field.getType());
        }
        //集合字段的注入
        if (isCollection) {
            setCollectionValue(field, initBeanDefine, canInject);
            return;
        }

        //注解有名字
        if (!annotation.value().equals("")) {
            boolean flag = false;
            for (BeanDefine beanDefine : canInject) {
                String name = beanDefine.getName();
                if (name.equals(annotation.value())) {
                    if (setValue(field, initBeanDefine, beanDefine)) {
                        flag = true;
                        break;
                    } else {
                        continue;
                    }
                }
            }
            if (flag) {
                return;
            } else {
                throw new XIocException("根据@Inject注解的名称无法找到符合类型的bean");
            }
        }
        //注解无名子
        if (annotation.value().equals("")) {
            boolean flag = false;
            for (BeanDefine beanDefine : canInject) {
                String name = beanDefine.getName();
                //根据字段名称
                if (name.equals(field.getName())) {
                    if (setValue(field, initBeanDefine, beanDefine)) {
                        flag = true;
                        break;
                    } else {
                        continue;
                    }
                }
            }
            //字段名称不同, 只能根据类型选择一个注入了
            if (!flag) {
                for (BeanDefine beanDefine : canInject) {
                    if (setValue(field, initBeanDefine, beanDefine)) {
                        break;
                    }
                }
            }
        }
    }

    /**
     * 集合字段依赖注入的方法, 反射赋值
     *
     * @param field
     * @param initBeanDefine 需要注入的对象
     * @param needInjects    容器中获得的被 注入的对象
     * @return
     */
    private boolean setCollectionValue(Field field, BeanDefine initBeanDefine, Collection<BeanDefine> needInjects) {
        try {
            Stream<Object> objectStream = needInjects.stream().map(beanDefine -> beanDefine.getBean());
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            if (Set.class.isAssignableFrom(field.getType())) {
                Set<Object> collect = objectStream.collect(Collectors.toSet());
                field.set(initBeanDefine.getBean(), collect);
            }
            List<Object> collect = objectStream.collect(Collectors.toList());
            field.set(initBeanDefine.getBean(), collect);
            return true;
        } catch (Exception e) {
            System.err.println("集合类型的注入: " + e.getMessage());
            return false;
        }
    }

    /**
     * 依赖注入的方法, 是通过调用   反射赋值
     *
     * @param field
     * @param initBeanDefine 需要注入的对象
     * @param container      容器中获得的被 注入的对象
     * @return
     */
    private boolean setValue(Field field, BeanDefine initBeanDefine, BeanDefine container) {
        try {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            field.set(initBeanDefine.getBean(), container.getBean());
            return true;
        } catch (Exception e) {
            System.err.println("出现问题: " + e.getMessage());
            return false;
        }
    }

    /**
     * 递归的方法找到某个目录下的所有class文件
     *
     * @param file        查找的目录
     * @param packageName 这个目录中的类对应的包名
     * @return
     */
    private List<String> finalClassName(File file, String packageName) {
        List<String> classNameList = new ArrayList<>();
        if (file.exists()) {
            //找到当前目录下的所有类文件
            File[] files = file.listFiles(f -> {
                boolean exists = f.exists();
                boolean isFile = f.isFile();
                boolean canRead = f.canRead();
                boolean isClass = f.getName().endsWith(".class");
                return exists && isFile && canRead && isClass;
            });

            //每个类处理下
            for (File classF : files) {
                String className = packageName + "." + classF.getName();
                className = className.substring(0, className.length() - 6);
                classNameList.add(className);
            }

            //找到当前目录下的下级目录
            File[] files1 = file.listFiles(f -> {
                boolean exists = f.exists();
                boolean isDirectory = f.isDirectory();
                boolean canRead = f.canRead();
                return exists && isDirectory && canRead;
            });

            for (File file1 : files1) {
                //递归处理所有的类
                List<String> list = finalClassName(file1, packageName + "." + file1.getName());
                classNameList.addAll(list);
            }
        }
        return classNameList;
    }


    /**
     * 初始化 bean, 加载到容器中
     *
     * @param className
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void initBeanDefine(String className) throws
            ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName(className);
        Annotation[] annotations = aClass.getAnnotations();

        if (annotations != null && annotations.length > 0) {
            annotationMap.put(aClass, annotations);
        }

        for (Annotation annotation : annotations) {
            if (annotation instanceof Component) {
                if (Annotation.class.isAssignableFrom(aClass)) {
                    customAnno.add(aClass);
                    continue;
                }
                Component component = (Component) annotation;
                String value = component.value();
                addToContainer(aClass, value, annotation);
                continue;
            } else if (annotation instanceof Configuration) {
                Configuration component = (Configuration) annotation;
                String value = component.value();
                addToContainer(aClass, value, annotation);
            }
        }
    }

    /**
     * @param aClass     bean的类型
     * @param name       bean的名称
     * @param annotation 注解
     * @param <T>
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private <T extends Annotation> void addToContainer(Class<?> aClass, String name, T annotation) throws
            InstantiationException, IllegalAccessException {
        this.addToContainer(aClass, name, annotation, aClass.newInstance());
    }


    public <T extends Annotation> void addToContainer(Class<?> aClass, String name, T annotation, Object obj) {
        this.addToContainer(aClass, name, annotation, obj, container);
    }

    public <T extends Annotation> void addToContainer(Class<?> aClass, String name, T annotation, Object
            obj, List<BeanDefine> container) {
        BeanDefine beanDefine = new BeanDefine();
        beanDefine.setAnnotation(annotation);
        beanDefine.settClass(aClass);
        if ("".equals(name)) {
            String simpleName = aClass.getSimpleName();
            String beanName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
            beanDefine.setName(beanName);
        } else {
            beanDefine.setName(name);
        }
        beanDefine.setBean(obj);
        container.add(beanDefine);
    }
}
